import { PanelOptions, Aggregation, Threshold, Icon, IconPosition, Condition } from 'types';

import { filterMetricListByAlias, getAggregatedValueFromSerie } from '../../utils';

import { getValueFormat } from '@grafana/data';

import _ from 'lodash';

// Convert Grafana options into Chartwerk Gauge options
export class GaugeOptions {
  private minValue: number | undefined;
  private maxValue: number | undefined;
  private thresholds: Array<{ value: number; color: string }> = [];
  private icons: Array<{ src: string; position: string; size: number }> = [];

  constructor(private grafanaSeriesList: any[], private grafanaOptions: PanelOptions) {
    this._setMin();
    this._setMax();
    this._setThresholds();
    this._setIcons();
  }

  private _setMin(): void {
    if (!this.grafanaOptions.gauge.min) {
      throw new Error(`Min Config is not selected: [See options: Extremum -> Min]`);
    }
    if (!this.grafanaOptions.gauge.min?.useMetric) {
      this.minValue = this.grafanaOptions.gauge.min.value;
      return;
    }
    const aggregatedValue = this.getLastValueFromMetrics(this.grafanaOptions.gauge.min.metricName, 'Min');
    this.minValue = aggregatedValue ? aggregatedValue : undefined;
  }

  private _setMax(): void {
    if (!this.grafanaOptions.gauge.max) {
      throw new Error(`Max Config is not selected: [See options: Extremum -> Max]`);
    }
    if (!this.grafanaOptions.gauge.max?.useMetric) {
      this.maxValue = this.grafanaOptions.gauge.max.value;
      return;
    }
    const aggregatedValue = this.getLastValueFromMetrics(this.grafanaOptions.gauge.max.metricName, 'Max');
    this.maxValue = aggregatedValue ? aggregatedValue : undefined;
  }

  private _setThresholds(): void {
    if (_.isEmpty(this.grafanaOptions.gauge.thresholds.thresholds)) {
      return;
    }
    for (let [idx, threshold] of this.grafanaOptions.gauge.thresholds.thresholds.entries()) {
      this._setThreshold(threshold, idx);
    }
  }

  private _setThreshold(threshold: Threshold, idx: number): void {
    const value = threshold.useMetric
      ? this.getLastValueFromMetrics(threshold.metricName, `Threshold ${idx + 1}`)
      : threshold.value;
    if (value === null || value === undefined) {
      // TODO: may be throw an error
      return;
    }
    this.thresholds.push({
      value,
      color: threshold.color,
    });
  }

  private _valueFormatter(value: number): string {
    const suffix = getValueFormat(this.grafanaOptions.gauge.unit)(0)?.suffix || '';
    const decimals = _.isNumber(this.grafanaOptions.gauge.decimals) ? this.grafanaOptions.gauge.decimals : 2;
    return `${value.toFixed(decimals)} ${suffix}`;
  }

  private _setIcons(): void {
    if (_.isEmpty(this.grafanaOptions.gauge.icons)) {
      return;
    }
    for (let [idx, icon] of this.grafanaOptions.gauge.icons.entries()) {
      this._setIcon(icon, idx);
    }
  }

  private _setIcon(icon: Icon, idx: number): void {
    if (!this._areIconConditionsFulfilled(icon, idx)) {
      return;
    }
    this.icons.push({
      src: icon.url,
      size: icon.size,
      position: this._getChartwerkIconPosition(icon.position),
    });
  }

  private _areIconConditionsFulfilled(icon: Icon, iconIdx: number): boolean {
    if (_.isEmpty(icon.metrics)) {
      return true;
    }

    // check each condition and return false if something goes wrong
    for (let [conditionIdx, metric] of icon.metrics.entries()) {
      const value = this.getLastValueFromMetrics(metric, `Icon ${iconIdx + 1}, Condition ${conditionIdx + 1}`);
      if (value === null || value === undefined) {
        // TODO: may be throw an error
        return false;
      }
      if (!this.checkIconCondition(value, icon.values[conditionIdx], icon.conditions[conditionIdx])) {
        return false;
      }
    }

    return true;
  }

  private checkIconCondition(metricValue: number, inputValue: number, condition: Condition): boolean {
    if (inputValue === undefined || inputValue === null) {
      return true;
    }
    switch (condition) {
      case Condition.EQUAL:
        return metricValue === inputValue;
      case Condition.GREATER:
        return metricValue > inputValue;
      case Condition.GREATER_OR_EQUAL:
        return metricValue >= inputValue;
      case Condition.LESS:
        return metricValue < inputValue;
      case Condition.LESS_OR_EQUAL:
        return metricValue <= inputValue;
      default:
        throw new Error(`Unknown condition: ${condition}`);
    }
  }

  private _getChartwerkIconPosition(position: IconPosition): string {
    // TODO: use chartwerk types
    switch (position) {
      case IconPosition.MIDDLE:
        return 'middle';
      case IconPosition.UPPER_LEFT:
        return 'left';
      case IconPosition.UPPER_RIGHT:
        return 'right';
      default:
        throw new Error(`Unknown Icon Position ${position}`);
    }
  }

  getChartwerkOptions(): any {
    return {
      maxValue: this.maxValue,
      minValue: this.minValue,
      valueFormatter: (val: number) => this._valueFormatter(val),
      defaultColor: this.grafanaOptions.gauge.thresholds.defaultColor,
      valueArcBackgroundColor: this.grafanaOptions.gauge.thresholds.arcBackground,
      reversed: this.grafanaOptions.gauge.reversed,
      stops: this.thresholds,
      valueFontSize: this.grafanaOptions.gauge.valueSize,
      icons: this.icons,
    };
  }

  getLastValueFromMetrics(metricName: string | undefined, optionName: string): number | null {
    // optionName -> helper in Error, mb use option path instead
    const filteredSeries = filterMetricListByAlias(this.grafanaSeriesList, metricName, optionName);
    const serie = filteredSeries[0];
    // Last value for now
    return getAggregatedValueFromSerie(serie, Aggregation.LAST);
  }
}
